/*-------------------------------------------------------------------------
 *
 * dict_thesaurus.c
 *		类词典：短语替换短语
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/tsearch/dict_thesaurus.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "catalog/namespace.h"
#include "commands/defrem.h"
#include "tsearch/ts_cache.h"
#include "tsearch/ts_locale.h"
#include "tsearch/ts_utils.h"
#include "utils/builtins.h"
#include "utils/regproc.h"


/*
 * 临时使用 TSLexeme.flags 供内部使用...
 */
#define DT_USEASIS		0x1000

typedef struct LexemeInfo
{
	uint32		idsubst;		/* 词典中的条目编号 DictThesaurus->subst */
	uint16		posinsubst;		/* 条目中的词性信息 */
	uint16		tnvariant;		/* 一个变体中的词素总数 */
	struct LexemeInfo *nextentry;
	struct LexemeInfo *nextvariant;
} LexemeInfo;

typedef struct
{
	char	   *lexeme;
	LexemeInfo *entries;
} TheLexeme;

typedef struct
{
	uint16		lastlexeme;		/* 要替换的词素数量 */
	uint16		reslen;
	TSLexeme   *res;			/* 准备好的替换结果 */
} TheSubstitute;

typedef struct
{
	/* 用于标准化词素的子词典 */
	Oid			subdictOid;
	TSDictionaryCacheEntry *subdict;

	/* 按精确匹配搜索词素的数组 */
	TheLexeme  *wrds;
	int			nwrds;			/* 当前单词数量 */
	int			ntwrds;			/* 分配的数组长度 */

	/*
	 * 替换结果的存储，第 n 个元素用于第 n 个表达式
	 */
	TheSubstitute *subst;
	int			nsubst;
} DictThesaurus;


static void fc_newLexeme(DictThesaurus *fc_d, char *fc_b, char *fc_e, uint32 fc_idsubst, uint16 fc_posinsubst)
{
	TheLexeme  *fc_ptr;

	if (fc_d->nwrds >= fc_d->ntwrds)
	{
		if (fc_d->ntwrds == 0)
		{
			fc_d->ntwrds = 16;
			fc_d->wrds = (TheLexeme *) palloc(sizeof(TheLexeme) * fc_d->ntwrds);
		}
		else
		{
			fc_d->ntwrds *= 2;
			fc_d->wrds = (TheLexeme *) repalloc(fc_d->wrds, sizeof(TheLexeme) * fc_d->ntwrds);
		}
	}

	fc_ptr = fc_d->wrds + fc_d->nwrds;
	fc_d->nwrds++;

	fc_ptr->lexeme = palloc(fc_e - fc_b + 1);

	memcpy(fc_ptr->lexeme, fc_b, fc_e - fc_b);
	fc_ptr->lexeme[fc_e - fc_b] = '\0';

	fc_ptr->entries = (LexemeInfo *) palloc(sizeof(LexemeInfo));

	fc_ptr->entries->nextentry = NULL;
	fc_ptr->entries->idsubst = fc_idsubst;
	fc_ptr->entries->posinsubst = fc_posinsubst;
}

static void fc_addWrd(DictThesaurus *fc_d, char *fc_b, char *fc_e, uint32 fc_idsubst, uint16 fc_nwrd, uint16 fc_posinsubst, bool fc_useasis)
{
	static int	fc_nres = 0;
	static int	fc_ntres = 0;
	TheSubstitute *fc_ptr;

	if (fc_nwrd == 0)
	{
		fc_nres = fc_ntres = 0;

		if (fc_idsubst >= fc_d->nsubst)
		{
			if (fc_d->nsubst == 0)
			{
				fc_d->nsubst = 16;
				fc_d->subst = (TheSubstitute *) palloc(sizeof(TheSubstitute) * fc_d->nsubst);
			}
			else
			{
				fc_d->nsubst *= 2;
				fc_d->subst = (TheSubstitute *) repalloc(fc_d->subst, sizeof(TheSubstitute) * fc_d->nsubst);
			}
		}
	}

	fc_ptr = fc_d->subst + fc_idsubst;

	fc_ptr->lastlexeme = fc_posinsubst - 1;

	if (fc_nres + 1 >= fc_ntres)
	{
		if (fc_ntres == 0)
		{
			fc_ntres = 2;
			fc_ptr->res = (TSLexeme *) palloc(sizeof(TSLexeme) * fc_ntres);
		}
		else
		{
			fc_ntres *= 2;
			fc_ptr->res = (TSLexeme *) repalloc(fc_ptr->res, sizeof(TSLexeme) * fc_ntres);
		}
	}

	fc_ptr->res[fc_nres].lexeme = palloc(fc_e - fc_b + 1);
	memcpy(fc_ptr->res[fc_nres].lexeme, fc_b, fc_e - fc_b);
	fc_ptr->res[fc_nres].lexeme[fc_e - fc_b] = '\0';

	fc_ptr->res[fc_nres].nvariant = fc_nwrd;
	if (fc_useasis)
		fc_ptr->res[fc_nres].flags = DT_USEASIS;
	else
		fc_ptr->res[fc_nres].flags = 0;

	fc_ptr->res[++fc_nres].lexeme = NULL;
}

#define TR_WAITLEX	1
#define TR_INLEX	2
#define TR_WAITSUBS 3
#define TR_INSUBS	4

static void fc_thesaurusRead(const char *fc_filename, DictThesaurus *fc_d)
{
	tsearch_readline_state fc_trst;
	uint32		fc_idsubst = 0;
	bool		fc_useasis = false;
	char	   *fc_line;

	fc_filename = get_tsearch_config_filename(fc_filename, "ths");
	if (!tsearch_readline_begin(&fc_trst, fc_filename))
		ereport(ERROR,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("could not open thesaurus file \"%s\": %m",
						fc_filename)));

	while ((fc_line = tsearch_readline(&fc_trst)) != NULL)
	{
		char	   *fc_ptr;
		int			fc_state = TR_WAITLEX;
		char	   *fc_beginwrd = NULL;
		uint32		fc_posinsubst = 0;
		uint32		fc_nwrd = 0;

		fc_ptr = fc_line;

		/* 这是一个注释吗？ */
		while (*fc_ptr && t_isspace(fc_ptr))
			fc_ptr += pg_mblen(fc_ptr);

		if (t_iseq(fc_ptr, '#') || *fc_ptr == '\0' ||
			t_iseq(fc_ptr, '\n') || t_iseq(fc_ptr, '\r'))
		{
			pfree(fc_line);
			continue;
		}

		while (*fc_ptr)
		{
			if (fc_state == TR_WAITLEX)
			{
				if (t_iseq(fc_ptr, ':'))
				{
					if (fc_posinsubst == 0)
						ereport(ERROR,
								(errcode(ERRCODE_CONFIG_FILE_ERROR),
								 errmsg("unexpected delimiter")));
					fc_state = TR_WAITSUBS;
				}
				else if (!t_isspace(fc_ptr))
				{
					fc_beginwrd = fc_ptr;
					fc_state = TR_INLEX;
				}
			}
			else if (fc_state == TR_INLEX)
			{
				if (t_iseq(fc_ptr, ':'))
				{
					fc_newLexeme(fc_d, fc_beginwrd, fc_ptr, fc_idsubst, fc_posinsubst++);
					fc_state = TR_WAITSUBS;
				}
				else if (t_isspace(fc_ptr))
				{
					fc_newLexeme(fc_d, fc_beginwrd, fc_ptr, fc_idsubst, fc_posinsubst++);
					fc_state = TR_WAITLEX;
				}
			}
			else if (fc_state == TR_WAITSUBS)
			{
				if (t_iseq(fc_ptr, '*'))
				{
					fc_useasis = true;
					fc_state = TR_INSUBS;
					fc_beginwrd = fc_ptr + pg_mblen(fc_ptr);
				}
				else if (t_iseq(fc_ptr, '\\'))
				{
					fc_useasis = false;
					fc_state = TR_INSUBS;
					fc_beginwrd = fc_ptr + pg_mblen(fc_ptr);
				}
				else if (!t_isspace(fc_ptr))
				{
					fc_useasis = false;
					fc_beginwrd = fc_ptr;
					fc_state = TR_INSUBS;
				}
			}
			else if (fc_state == TR_INSUBS)
			{
				if (t_isspace(fc_ptr))
				{
					if (fc_ptr == fc_beginwrd)
						ereport(ERROR,
								(errcode(ERRCODE_CONFIG_FILE_ERROR),
								 errmsg("unexpected end of line or lexeme")));
					fc_addWrd(fc_d, fc_beginwrd, fc_ptr, fc_idsubst, fc_nwrd++, fc_posinsubst, fc_useasis);
					fc_state = TR_WAITSUBS;
				}
			}
			else
				elog(ERROR, "unrecognized thesaurus state: %d", fc_state);

			fc_ptr += pg_mblen(fc_ptr);
		}

		if (fc_state == TR_INSUBS)
		{
			if (fc_ptr == fc_beginwrd)
				ereport(ERROR,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("unexpected end of line or lexeme")));
			fc_addWrd(fc_d, fc_beginwrd, fc_ptr, fc_idsubst, fc_nwrd++, fc_posinsubst, fc_useasis);
		}

		fc_idsubst++;

		if (!(fc_nwrd && fc_posinsubst))
			ereport(ERROR,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("unexpected end of line")));

		if (fc_nwrd != (uint16) fc_nwrd || fc_posinsubst != (uint16) fc_posinsubst)
			ereport(ERROR,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("too many lexemes in thesaurus entry")));

		pfree(fc_line);
	}

	fc_d->nsubst = fc_idsubst;

	tsearch_readline_end(&fc_trst);
}

static TheLexeme *
fc_addCompiledLexeme(TheLexeme *fc_newwrds, int *fc_nnw, int *fc_tnm, TSLexeme *fc_lexeme, LexemeInfo *fc_src, uint16 fc_tnvariant)
{
	if (*fc_nnw >= *fc_tnm)
	{
		*fc_tnm *= 2;
		fc_newwrds = (TheLexeme *) repalloc(fc_newwrds, sizeof(TheLexeme) * *fc_tnm);
	}

	fc_newwrds[*fc_nnw].entries = (LexemeInfo *) palloc(sizeof(LexemeInfo));

	if (fc_lexeme && fc_lexeme->lexeme)
	{
		fc_newwrds[*fc_nnw].lexeme = pstrdup(fc_lexeme->lexeme);
		fc_newwrds[*fc_nnw].entries->tnvariant = fc_tnvariant;
	}
	else
	{
		fc_newwrds[*fc_nnw].lexeme = NULL;
		fc_newwrds[*fc_nnw].entries->tnvariant = 1;
	}

	fc_newwrds[*fc_nnw].entries->idsubst = fc_src->idsubst;
	fc_newwrds[*fc_nnw].entries->posinsubst = fc_src->posinsubst;

	fc_newwrds[*fc_nnw].entries->nextentry = NULL;

	(*fc_nnw)++;
	return fc_newwrds;
}

static int fc_cmpLexemeInfo(LexemeInfo *fc_a, LexemeInfo *fc_b)
{
	if (fc_a == NULL || fc_b == NULL)
		return 0;

	if (fc_a->idsubst == fc_b->idsubst)
	{
		if (fc_a->posinsubst == fc_b->posinsubst)
		{
			if (fc_a->tnvariant == fc_b->tnvariant)
				return 0;

			return (fc_a->tnvariant > fc_b->tnvariant) ? 1 : -1;
		}

		return (fc_a->posinsubst > fc_b->posinsubst) ? 1 : -1;
	}

	return (fc_a->idsubst > fc_b->idsubst) ? 1 : -1;
}

static int fc_cmpLexeme(const TheLexeme *fc_a, const TheLexeme *fc_b)
{
	if (fc_a->lexeme == NULL)
	{
		if (fc_b->lexeme == NULL)
			return 0;
		else
			return 1;
	}
	else if (fc_b->lexeme == NULL)
		return -1;

	return strcmp(fc_a->lexeme, fc_b->lexeme);
}

static int fc_cmpLexemeQ(const void *fc_a, const void *fc_b)
{
	return fc_cmpLexeme((const TheLexeme *) fc_a, (const TheLexeme *) fc_b);
}

static int fc_cmpTheLexeme(const void *fc_a, const void *fc_b)
{
	const TheLexeme *fc_la = (const TheLexeme *) fc_a;
	const TheLexeme *fc_lb = (const TheLexeme *) fc_b;
	int			fc_res;

	if ((fc_res = fc_cmpLexeme(fc_la, fc_lb)) != 0)
		return fc_res;

	return -fc_cmpLexemeInfo(fc_la->entries, fc_lb->entries);
}

static void fc_compileTheLexeme(DictThesaurus *fc_d)
{
	int			fc_i,
				fc_nnw = 0,
				fc_tnm = 16;
	TheLexeme  *fc_newwrds = (TheLexeme *) palloc(sizeof(TheLexeme) * fc_tnm),
			   *fc_ptrwrds;

	for (fc_i = 0; fc_i < fc_d->nwrds; fc_i++)
	{
		TSLexeme   *fc_ptr;

		if (strcmp(fc_d->wrds[fc_i].lexeme, "?") == 0)	/* 是停用词标记吗？ */
			fc_newwrds = fc_addCompiledLexeme(fc_newwrds, &fc_nnw, &fc_tnm, NULL, fc_d->wrds[fc_i].entries, 0);
		else
		{
			fc_ptr = (TSLexeme *) DatumGetPointer(FunctionCall4(&(fc_d->subdict->lexize),
															 PointerGetDatum(fc_d->subdict->dictData),
															 PointerGetDatum(fc_d->wrds[fc_i].lexeme),
															 Int32GetDatum(strlen(fc_d->wrds[fc_i].lexeme)),
															 PointerGetDatum(NULL)));

			if (!fc_ptr)
				ereport(ERROR,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("thesaurus sample word \"%s\" isn't recognized by subdictionary (rule %d)",
								fc_d->wrds[fc_i].lexeme,
								fc_d->wrds[fc_i].entries->idsubst + 1)));
			else if (!(fc_ptr->lexeme))
				ereport(ERROR,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("thesaurus sample word \"%s\" is a stop word (rule %d)",
								fc_d->wrds[fc_i].lexeme,
								fc_d->wrds[fc_i].entries->idsubst + 1),
						 errhint("Use \"?\" to represent a stop word within a sample phrase.")));
			else
			{
				while (fc_ptr->lexeme)
				{
					TSLexeme   *fc_remptr = fc_ptr + 1;
					int			fc_tnvar = 1;
					int			fc_curvar = fc_ptr->nvariant;

					/* 计算一个变体中的 n 个单词 */
					while (fc_remptr->lexeme)
					{
						if (fc_remptr->nvariant != (fc_remptr - 1)->nvariant)
							break;
						fc_tnvar++;
						fc_remptr++;
					}

					fc_remptr = fc_ptr;
					while (fc_remptr->lexeme && fc_remptr->nvariant == fc_curvar)
					{
						fc_newwrds = fc_addCompiledLexeme(fc_newwrds, &fc_nnw, &fc_tnm, fc_remptr, fc_d->wrds[fc_i].entries, fc_tnvar);
						fc_remptr++;
					}

					fc_ptr = fc_remptr;
				}
			}
		}

		pfree(fc_d->wrds[fc_i].lexeme);
		pfree(fc_d->wrds[fc_i].entries);
	}

	if (fc_d->wrds)
		pfree(fc_d->wrds);
	fc_d->wrds = fc_newwrds;
	fc_d->nwrds = fc_nnw;
	fc_d->ntwrds = fc_tnm;

	if (fc_d->nwrds > 1)
	{
		qsort(fc_d->wrds, fc_d->nwrds, sizeof(TheLexeme), fc_cmpTheLexeme);

		/* 唯一的 */
		fc_newwrds = fc_d->wrds;
		fc_ptrwrds = fc_d->wrds + 1;
		while (fc_ptrwrds - fc_d->wrds < fc_d->nwrds)
		{
			if (fc_cmpLexeme(fc_ptrwrds, fc_newwrds) == 0)
			{
				if (fc_cmpLexemeInfo(fc_ptrwrds->entries, fc_newwrds->entries))
				{
					fc_ptrwrds->entries->nextentry = fc_newwrds->entries;
					fc_newwrds->entries = fc_ptrwrds->entries;
				}
				else
					pfree(fc_ptrwrds->entries);

				if (fc_ptrwrds->lexeme)
					pfree(fc_ptrwrds->lexeme);
			}
			else
			{
				fc_newwrds++;
				*fc_newwrds = *fc_ptrwrds;
			}

			fc_ptrwrds++;
		}

		fc_d->nwrds = fc_newwrds - fc_d->wrds + 1;
		fc_d->wrds = (TheLexeme *) repalloc(fc_d->wrds, sizeof(TheLexeme) * fc_d->nwrds);
	}
}

static void fc_compileTheSubstitute(DictThesaurus *fc_d)
{
	int			fc_i;

	for (fc_i = 0; fc_i < fc_d->nsubst; fc_i++)
	{
		TSLexeme   *fc_rem = fc_d->subst[fc_i].res,
				   *fc_outptr,
				   *fc_inptr;
		int			fc_n = 2;

		fc_outptr = fc_d->subst[fc_i].res = (TSLexeme *) palloc(sizeof(TSLexeme) * fc_n);
		fc_outptr->lexeme = NULL;
		fc_inptr = fc_rem;

		while (fc_inptr && fc_inptr->lexeme)
		{
			TSLexeme   *fc_lexized,
						fc_tmplex[2];

			if (fc_inptr->flags & DT_USEASIS)
			{					/* 不进行词素化 */
				fc_tmplex[0] = *fc_inptr;
				fc_tmplex[0].flags = 0;
				fc_tmplex[1].lexeme = NULL;
				fc_lexized = fc_tmplex;
			}
			else
			{
				fc_lexized = (TSLexeme *) DatumGetPointer(FunctionCall4(&(fc_d->subdict->lexize),
																	 PointerGetDatum(fc_d->subdict->dictData),
																	 PointerGetDatum(fc_inptr->lexeme),
																	 Int32GetDatum(strlen(fc_inptr->lexeme)),
																	 PointerGetDatum(NULL)));
			}

			if (fc_lexized && fc_lexized->lexeme)
			{
				int			fc_toset = (fc_lexized->lexeme && fc_outptr != fc_d->subst[fc_i].res) ? (fc_outptr - fc_d->subst[fc_i].res) : -1;

				while (fc_lexized->lexeme)
				{
					if (fc_outptr - fc_d->subst[fc_i].res + 1 >= fc_n)
					{
						int			fc_diff = fc_outptr - fc_d->subst[fc_i].res;

						fc_n *= 2;
						fc_d->subst[fc_i].res = (TSLexeme *) repalloc(fc_d->subst[fc_i].res, sizeof(TSLexeme) * fc_n);
						fc_outptr = fc_d->subst[fc_i].res + fc_diff;
					}

					*fc_outptr = *fc_lexized;
					fc_outptr->lexeme = pstrdup(fc_lexized->lexeme);

					fc_outptr++;
					fc_lexized++;
				}

				if (fc_toset > 0)
					fc_d->subst[fc_i].res[fc_toset].flags |= TSL_ADDPOS;
			}
			else if (fc_lexized)
			{
				ereport(ERROR,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("thesaurus substitute word \"%s\" is a stop word (rule %d)",
								fc_inptr->lexeme, fc_i + 1)));
			}
			else
			{
				ereport(ERROR,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("thesaurus substitute word \"%s\" isn't recognized by subdictionary (rule %d)",
								fc_inptr->lexeme, fc_i + 1)));
			}

			if (fc_inptr->lexeme)
				pfree(fc_inptr->lexeme);
			fc_inptr++;
		}

		if (fc_outptr == fc_d->subst[fc_i].res)
			ereport(ERROR,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("thesaurus substitute phrase is empty (rule %d)",
							fc_i + 1)));

		fc_d->subst[fc_i].reslen = fc_outptr - fc_d->subst[fc_i].res;

		pfree(fc_rem);
	}
}

Datum thesaurus_init(PG_FUNCTION_ARGS)
{
	List	   *fc_dictoptions = (List *) PG_GETARG_POINTER(0);
	DictThesaurus *fc_d;
	char	   *fc_subdictname = NULL;
	bool		fc_fileloaded = false;
	ListCell   *fc_l;

	fc_d = (DictThesaurus *) palloc0(sizeof(DictThesaurus));

	foreach(fc_l, fc_dictoptions)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_l);

		if (strcmp(fc_defel->defname, "dictfile") == 0)
		{
			if (fc_fileloaded)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("multiple DictFile parameters")));
			fc_thesaurusRead(defGetString(fc_defel), fc_d);
			fc_fileloaded = true;
		}
		else if (strcmp(fc_defel->defname, "dictionary") == 0)
		{
			if (fc_subdictname)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("multiple Dictionary parameters")));
			fc_subdictname = pstrdup(defGetString(fc_defel));
		}
		else
		{
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("unrecognized Thesaurus parameter: \"%s\"",
							fc_defel->defname)));
		}
	}

	if (!fc_fileloaded)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("missing DictFile parameter")));
	if (!fc_subdictname)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("missing Dictionary parameter")));

	fc_d->subdictOid = get_ts_dict_oid(stringToQualifiedNameList(fc_subdictname), false);
	fc_d->subdict = lookup_ts_dictionary_cache(fc_d->subdictOid);

	fc_compileTheLexeme(fc_d);
	fc_compileTheSubstitute(fc_d);

	PG_RETURN_POINTER(fc_d);
}

static LexemeInfo *
fc_findTheLexeme(DictThesaurus *fc_d, char *fc_lexeme)
{
	TheLexeme	fc_key,
			   *fc_res;

	if (fc_d->nwrds == 0)
		return NULL;

	fc_key.lexeme = fc_lexeme;
	fc_key.entries = NULL;

	fc_res = bsearch(&fc_key, fc_d->wrds, fc_d->nwrds, sizeof(TheLexeme), fc_cmpLexemeQ);

	if (fc_res == NULL)
		return NULL;
	return fc_res->entries;
}

static bool fc_matchIdSubst(LexemeInfo *fc_stored, uint32 fc_idsubst)
{
	bool		fc_res = true;

	if (fc_stored)
	{
		fc_res = false;

		for (; fc_stored; fc_stored = fc_stored->nextvariant)
			if (fc_stored->idsubst == fc_idsubst)
			{
				fc_res = true;
				break;
			}
	}

	return fc_res;
}

static LexemeInfo *
fc_findVariant(LexemeInfo *fc_in, LexemeInfo *fc_stored, uint16 fc_curpos, LexemeInfo **fc_newin, int fc_newn)
{
	for (;;)
	{
		int			fc_i;
		LexemeInfo *fc_ptr = fc_newin[0];

		for (fc_i = 0; fc_i < fc_newn; fc_i++)
		{
			while (fc_newin[fc_i] && fc_newin[fc_i]->idsubst < fc_ptr->idsubst)
				fc_newin[fc_i] = fc_newin[fc_i]->nextentry;

			if (fc_newin[fc_i] == NULL)
				return fc_in;

			if (fc_newin[fc_i]->idsubst > fc_ptr->idsubst)
			{
				fc_ptr = fc_newin[fc_i];
				fc_i = -1;
				continue;
			}

			while (fc_newin[fc_i]->idsubst == fc_ptr->idsubst)
			{
				if (fc_newin[fc_i]->posinsubst == fc_curpos && fc_newin[fc_i]->tnvariant == fc_newn)
				{
					fc_ptr = fc_newin[fc_i];
					break;
				}

				fc_newin[fc_i] = fc_newin[fc_i]->nextentry;
				if (fc_newin[fc_i] == NULL)
					return fc_in;
			}

			if (fc_newin[fc_i]->idsubst != fc_ptr->idsubst)
			{
				fc_ptr = fc_newin[fc_i];
				fc_i = -1;
				continue;
			}
		}

		if (fc_i == fc_newn && fc_matchIdSubst(fc_stored, fc_ptr->idsubst) && (fc_in == NULL || !fc_matchIdSubst(fc_in, fc_ptr->idsubst)))
		{						/* 找到了 */

			fc_ptr->nextvariant = fc_in;
			fc_in = fc_ptr;
		}

		/* 向前一步 */
		for (fc_i = 0; fc_i < fc_newn; fc_i++)
			fc_newin[fc_i] = fc_newin[fc_i]->nextentry;
	}
}

static TSLexeme * fc_copyTSLexeme(TheSubstitute *fc_ts)
{
	TSLexeme   *fc_res;
	uint16		fc_i;

	fc_res = (TSLexeme *) palloc(sizeof(TSLexeme) * (fc_ts->reslen + 1));
	for (fc_i = 0; fc_i < fc_ts->reslen; fc_i++)
	{
		fc_res[fc_i] = fc_ts->res[fc_i];
		fc_res[fc_i].lexeme = pstrdup(fc_ts->res[fc_i].lexeme);
	}

	fc_res[fc_ts->reslen].lexeme = NULL;

	return fc_res;
}

static TSLexeme * fc_checkMatch(DictThesaurus *fc_d, LexemeInfo *fc_info, uint16 fc_curpos, bool *fc_moreres)
{
	*fc_moreres = false;
	while (fc_info)
	{
		Assert(fc_info->idsubst < fc_d->nsubst);
		if (fc_info->nextvariant)
			*fc_moreres = true;
		if (fc_d->subst[fc_info->idsubst].lastlexeme == fc_curpos)
			return fc_copyTSLexeme(fc_d->subst + fc_info->idsubst);
		fc_info = fc_info->nextvariant;
	}

	return NULL;
}

Datum thesaurus_lexize(PG_FUNCTION_ARGS)
{
	DictThesaurus *fc_d = (DictThesaurus *) PG_GETARG_POINTER(0);
	DictSubState *fc_dstate = (DictSubState *) PG_GETARG_POINTER(3);
	TSLexeme   *fc_res = NULL;
	LexemeInfo *fc_stored,
			   *fc_info = NULL;
	uint16		fc_curpos = 0;
	bool		fc_moreres = false;

	if (PG_NARGS() != 4 || fc_dstate == NULL)
		elog(ERROR, "forbidden call of thesaurus or nested call");

	if (fc_dstate->isend)
		PG_RETURN_POINTER(NULL);
	fc_stored = (LexemeInfo *) fc_dstate->private_state;

	if (fc_stored)
		fc_curpos = fc_stored->posinsubst + 1;

	if (!fc_d->subdict->isvalid)
		fc_d->subdict = lookup_ts_dictionary_cache(fc_d->subdictOid);

	fc_res = (TSLexeme *) DatumGetPointer(FunctionCall4(&(fc_d->subdict->lexize),
													 PointerGetDatum(fc_d->subdict->dictData),
													 PG_GETARG_DATUM(1),
													 PG_GETARG_DATUM(2),
													 PointerGetDatum(NULL)));

	if (fc_res && fc_res->lexeme)
	{
		TSLexeme   *fc_ptr = fc_res,
				   *fc_basevar;

		while (fc_ptr->lexeme)
		{
			uint16		fc_nv = fc_ptr->nvariant;
			uint16		fc_i,
						fc_nlex = 0;
			LexemeInfo **fc_infos;

			fc_basevar = fc_ptr;
			while (fc_ptr->lexeme && fc_nv == fc_ptr->nvariant)
			{
				fc_nlex++;
				fc_ptr++;
			}

			fc_infos = (LexemeInfo **) palloc(sizeof(LexemeInfo *) * fc_nlex);
			for (fc_i = 0; fc_i < fc_nlex; fc_i++)
				if ((fc_infos[fc_i] = fc_findTheLexeme(fc_d, fc_basevar[fc_i].lexeme)) == NULL)
					break;

			if (fc_i < fc_nlex)
			{
				/* 没有机会找到 */
				pfree(fc_infos);
				continue;
			}

			fc_info = fc_findVariant(fc_info, fc_stored, fc_curpos, fc_infos, fc_nlex);
		}
	}
	else if (fc_res)
	{							/* 停用词 */
		LexemeInfo *fc_infos = fc_findTheLexeme(fc_d, NULL);

		fc_info = fc_findVariant(NULL, fc_stored, fc_curpos, &fc_infos, 1);
	}
	else
	{
		fc_info = NULL;			/* 单词未被识别 */
	}

	fc_dstate->private_state = (void *) fc_info;

	if (!fc_info)
	{
		fc_dstate->getnext = false;
		PG_RETURN_POINTER(NULL);
	}

	if ((fc_res = fc_checkMatch(fc_d, fc_info, fc_curpos, &fc_moreres)) != NULL)
	{
		fc_dstate->getnext = fc_moreres;
		PG_RETURN_POINTER(fc_res);
	}

	fc_dstate->getnext = true;

	PG_RETURN_POINTER(NULL);
}
